package eva

import (
	"testing"
	"gitcode.com/deyiyangyang/gascheme/lex"
	"gitcode.com/deyiyangyang/gascheme/obj"
	"gitcode.com/deyiyangyang/gascheme/par"
)

func TestApply(t *testing.T) {
	tests := []struct {
		input    string
		expected interface{}
	}{
		{`(apply + '(1 2 3))`, 6},
		{`(apply + 1 2 '(3 4))`, 10},
		{`(apply list '(1 2 3))`, []int{1, 2, 3}},
		{`(apply cons '(1 2))`, []int{1, 2}}, // This creates a dotted pair
	}

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		parser := par.NewParser(lexer)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser error: %s", err)
		}

		env := obj.NewGlobalEnvironment()
		RegisterBuiltinProcedures(env)

		result := Eval(expr, env)

		switch expected := tt.expected.(type) {
		case int:
			integer, ok := result.(*obj.Integer)
			if !ok {
				t.Fatalf("expected integer, got %T", result)
			}
			if integer.Value != int64(expected) {
				t.Errorf("expected %d, got %d", expected, integer.Value)
			}
		case []int:
			list, ok := result.(*obj.List)
			if !ok {
				t.Fatalf("expected list, got %T", result)
			}
			elements := list.ToSlice()
			expectedSlice := tt.expected.([]int)
			if len(elements) != len(expectedSlice) {
				t.Fatalf("expected list of length %d, got %d", len(expectedSlice), len(elements))
			}
			for i, expectedVal := range expectedSlice {
				integer, ok := elements[i].(*obj.Integer)
				if !ok {
					t.Fatalf("expected integer at index %d, got %T", i, elements[i])
				}
				if integer.Value != int64(expectedVal) {
					t.Errorf("expected %d at index %d, got %d", expectedVal, i, integer.Value)
				}
			}

		}
	}
}

func TestApplyErrorCases(t *testing.T) {
	tests := []string{
		`(apply)`,
		`(apply +)`,
		`(apply 1 '(1 2))`,
		`(apply + 1)`,
	}

	for _, input := range tests {
		lexer := lex.NewLexer(input)
		parser := par.NewParser(lexer)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser error: %s", err)
		}

		env := obj.NewGlobalEnvironment()
		RegisterBuiltinProcedures(env)

		result := Eval(expr, env)

		_, ok := result.(*obj.Error)
		if !ok {
			t.Errorf("expected error for input %s, got %T", input, result)
		}
	}
}

func TestMap(t *testing.T) {
	tests := []struct {
		input    string
		expected []int
	}{
		{`(map + '(1 2 3) '(4 5 6))`, []int{5, 7, 9}},
		{`(map (lambda (x) (* x x)) '(1 2 3))`, []int{1, 4, 9}},
		{`(map car '((1 2) (3 4) (5 6)))`, []int{1, 3, 5}},
	}

	for _, tt := range tests {
		lexer := lex.NewLexer(tt.input)
		parser := par.NewParser(lexer)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser error: %s", err)
		}

		env := obj.NewGlobalEnvironment()
		RegisterBuiltinProcedures(env)

		result := Eval(expr, env)

		list, ok := result.(*obj.List)
		if !ok {
			t.Fatalf("expected list, got %T", result)
		}
		
		elements := list.ToSlice()
		expectedSlice := tt.expected

		if len(elements) != len(expectedSlice) {
			t.Fatalf("expected list of length %d, got %d", len(expectedSlice), len(elements))
		}

		for i, expectedVal := range expectedSlice {
			integer, ok := elements[i].(*obj.Integer)
			if !ok {
				t.Fatalf("expected integer at index %d, got %T", i, elements[i])
			}
			if integer.Value != int64(expectedVal) {
				t.Errorf("expected %d at index %d, got %d", expectedVal, i, integer.Value)
			}
		}
	}
	
	// Special test for list function which returns lists
	listTestInput := `(map list '(1 2 3))`
	lexer := lex.NewLexer(listTestInput)
	parser := par.NewParser(lexer)
	expr, err := parser.ParseExpression()
	if err != nil {
		t.Fatalf("parser error: %s", err)
	}

	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	result := Eval(expr, env)

	list, ok := result.(*obj.List)
	if !ok {
		t.Fatalf("expected list, got %T", result)
	}

	elements := list.ToSlice()
	if len(elements) != 3 {
		t.Fatalf("expected list of length 3, got %d", len(elements))
	}

	// Each element should be a list containing one integer
	for i, expectedVal := range []int{1, 2, 3} {
		innerList, ok := elements[i].(*obj.List)
		if !ok {
			t.Fatalf("expected list at index %d, got %T", i, elements[i])
		}
		innerElements := innerList.ToSlice()
		if len(innerElements) != 1 {
			t.Fatalf("expected inner list of length 1 at index %d, got %d", i, len(innerElements))
		}
		integer, ok := innerElements[0].(*obj.Integer)
		if !ok {
			t.Fatalf("expected integer in inner list at index %d, got %T", i, innerElements[0])
		}
		if integer.Value != int64(expectedVal) {
			t.Errorf("expected %d in inner list at index %d, got %d", expectedVal, i, integer.Value)
		}
	}
}

func TestMapNestedLists(t *testing.T) {
	lexer := lex.NewLexer(`(map list '((1) (2) (3)))`)
	parser := par.NewParser(lexer)
	expr, err := parser.ParseExpression()
	if err != nil {
		t.Fatalf("parser error: %s", err)
	}

	env := obj.NewGlobalEnvironment()
	RegisterBuiltinProcedures(env)

	result := Eval(expr, env)
	list, ok := result.(*obj.List)
	if !ok {
		t.Fatalf("expected list, got %T", result)
	}
	
	elements := list.ToSlice()

	if len(elements) != 3 {
		t.Fatalf("expected list of length 3, got %d", len(elements))
	}

	// Each element should be a list containing one list containing one integer
	for i, expectedVal := range []int{1, 2, 3} {
		outerList, ok := elements[i].(*obj.List)
		if !ok {
			t.Fatalf("expected list at index %d, got %T", i, elements[i])
		}
		outerElements := outerList.ToSlice()
		if len(outerElements) != 1 {
			t.Fatalf("expected outer list of length 1 at index %d, got %d", i, len(outerElements))
		}
		innerList, ok := outerElements[0].(*obj.List)
		if !ok {
			t.Fatalf("expected inner list at index %d, got %T", i, outerElements[0])
		}
		innerElements := innerList.ToSlice()
		if len(innerElements) != 1 {
			t.Fatalf("expected inner list of length 1 at index %d, got %d", i, len(innerElements))
		}
		integer, ok := innerElements[0].(*obj.Integer)
		if !ok {
			t.Fatalf("expected integer in inner list at index %d, got %T", i, innerElements[0])
		}
		if integer.Value != int64(expectedVal) {
			t.Errorf("expected %d in inner list at index %d, got %d", expectedVal, i, integer.Value)
		}
	}
}

func TestMapErrorCases(t *testing.T) {
	tests := []string{
		`(map)`,
		`(map +)`,
		`(map 1 '(1 2))`,
		`(map + '(1 2) '(1 2 3))`,
	}

	for _, input := range tests {
		lexer := lex.NewLexer(input)
		parser := par.NewParser(lexer)
		expr, err := parser.ParseExpression()
		if err != nil {
			t.Fatalf("parser error: %s", err)
		}

		env := obj.NewGlobalEnvironment()
		RegisterBuiltinProcedures(env)

		result := Eval(expr, env)

		_, ok := result.(*obj.Error)
		if !ok {
			t.Errorf("expected error for input %s, got %T", input, result)
		}
	}
}